/**
* @file app_light_control.c
* @brief Common process - adapter the flash api
* @version 0.1
* @date 2021-10-22
*
* @copyright Copyright 2020-2021 Tuya Inc. All Rights Reserved.
*
*/

#include "tal_log.h"
#include "tal_memory.h"
#include "tal_system.h"
#include "tal_pwm.h"
#include "tal_flash.h"
#include "tal_watchdog.h"
#include "tal_nv_flash.h"
#include "tal_sw_timer.h"
#include "tal_attribute_rw.h"
#include "app_light_control.h"
#include "app_common.h"
#include "app_cluster_level.h"
#include "app_cluster_color_control.h"

/// control power gain (100 --> gain = 1 times )
#define CTRL_POEWER_GAIN_MIN 100
/// control power gain (200 --> gain = 2 times )
#define CTRL_POEWER_GAIN_MAX 200

/// control CW&RGB bright limit max
#define CTRL_BRIGHT_LIMIT_MAX 100
/// control CW&RGB bright limit min
#define CTRL_BRIGHT_LIMIT_MIN 0

/// control CW&RGB temper limit max
#define CTRL_TEMPER_LIMIT_MAX 100
/// control CW&RGB temper limit min
#define CTRL_TEMPER_LIMIT_MIN 0

#define MAX_DUTY 1000 ///< PWM MAX Duty 1000 --> Precision 0.1%

#define SHADE_STEP_GAIN_DEFAULT (CTRL_CAL_VALUE_RANGE * LIGHT_SHADE_CYCLE / SHADE_CHANG_MAX_TIME)

#define BREATH_TIMER_CYCLE_MS 10 //ms
#define BREATH_STEP_VALUE CTRL_CAL_VALUE_RANGE / (uiBreathTimeMs / BREATH_TIMER_CYCLE_MS)
#define SHADE_SLOPE_DEFAULT SHADE_STEP_GAIN_DEFAULT / LIGHT_SHADE_CYCLE

volatile STATIC UINT8_T identify_processing_flag = FALSE;

STATIC LIGHT_CTRL_DATA_T sg_light_ctrl_data;
STATIC light_ctrl_handle_t sg_light_ctrl_handle_data;

STATIC ULONG_T sg_blink_time_ms = 0;
STATIC UCHAR_T sg_light_channel = 0;

extern UINT8_T g_user_pwm_init_flag;
extern TUYA_PWM_POLARITY_E g_pwm_bPolarity;

extern TIMER_ID etimer_clear_rst;
extern TIMER_ID etimer_blink_sw;
extern TIMER_ID etimer_countdown;
extern TIMER_ID etimer_shande_param;
extern TIMER_ID etimer_shande_rgbcw;
extern TIMER_ID etimer_join_start_delay;
extern TIMER_ID etimer_save_data_delay;
extern TIMER_ID etimer_mf_blink;
extern TIMER_ID etimer_join_end_sync;

// extern UINT8_T g_power_on_sync;
extern UINT16_T g_power_on_bright;
extern UINT16_T g_power_on_temperature;

STATIC ULONG_T sg_step_gain = SHADE_STEP_GAIN_DEFAULT;
STATIC USHORT_T sg_shade_off_time = 1000; ///< shade time ms
STATIC USHORT_T sg_shade_on_time = 1000;
STATIC UINT8_T sg_shade_flag = FALSE;
STATIC FLOAT_T sg_step_gain_float;
STATIC USHORT_T sg_usRemainTime100ms = 0;
STATIC ULONG_T sg_transtime_100ms = 0;
STATIC UINT8_T sg_level_move_down_to_off = FALSE;

/**
 * @note: CW bright output limit process
 * @param {IN USHORT_T Bright -> bright value}
 * @return: USHORT_T usResult
 */
STATIC USHORT_T __app_light_ctrl_data_cw_limit(USHORT_T bright)
{
    USHORT_T max = 0, min = 0, result = 0;

    max = CTRL_CW_BRIGHT_VALUE_MAX;
    min = 10;

    result = (USHORT_T)((bright - CTRL_CW_BRIGHT_VALUE_MIN) * (max - min) /
                            (CTRL_CW_BRIGHT_VALUE_MAX - CTRL_CW_BRIGHT_VALUE_MIN) +
                        min);

    return result;
}
/**
 * @note: shade function
 * @param  none 
 */
STATIC VOID_T __app_light_ctrl_start(VOID_T)
{
    sg_light_ctrl_handle_data.first_change_flag = TRUE;
    sg_shade_flag = TRUE;
    TAL_PR_DEBUG("ShadeParam >>> %d", sg_light_ctrl_handle_data.shade_param);
    switch (sg_light_ctrl_handle_data.shade_param)
    {
    case SHADE_PARAM_B:
    case SHADE_PARAM_T: //1,2
    {
        tal_sw_timer_stop(etimer_shande_rgbcw);
        tal_sw_timer_start(etimer_shande_param, 0, TAL_TIMER_ONCE);
        break;
    }
    case SHADE_PARAM_RGBCW: //0
    {
        tal_sw_timer_stop(etimer_shande_param);
        tal_sw_timer_start(etimer_shande_rgbcw, 0, TAL_TIMER_ONCE);
        break;
    }
    default:
        break;
    }
}
/**
 * @berief: pwm soc set positive duty
 * @param {ULONG_T duty --> pwm send duty}
 * @param {UCHAR_T channel --> pwm send out channel}
 * @attention channel --> according to the initlize order
 * @return: none
 */
STATIC VOID_T __app_ctrl_pwm_duty_set(UCHAR_T channel, USHORT_T duty)
{
    //TAL_PR_DEBUG("channel = %d , duty = %d\n", channel, duty);
    if (duty > MAX_DUTY)
    {
        TAL_PR_DEBUG("PWM SET DUTY ERR");
        return;
    }
    if (channel >= sg_light_channel)
    {
        TAL_PR_DEBUG("PWM SET DUTY CHANNEL ERR");
        return;
    }

    TUYA_PWM_BASE_CFG_T v_dcb = {
        .duty = duty,
        .frequency = 1000,
        .polarity = g_pwm_bPolarity,
    };

    if (OPRT_OK != tal_pwm_info_set((TUYA_PWM_NUM_E)channel, &v_dcb))
    {
        TAL_PR_DEBUG("PWM SET DUTY ERR");
    }
}

/**
 * @note: light send control data
 * @param {usR_value} red color, range 0~1000
 * @param {usG_Value} Green color, range 0~1000
 * @param {usB_Value} Blue color, range 0~1000
 * @param {usC_Value} cold white color, range 0~1000
 * @param {usW_Value} warm white color, range 0~1000
 * @return: OPERATE_RET
 */
STATIC OPERATE_RET __app_light_ctrl_rgbcw_set(USHORT_T r_value, USHORT_T g_value, USHORT_T b_value, USHORT_T c_value, USHORT_T w_value)
{
    if (g_user_pwm_init_flag != TRUE)
    {
        TAL_PR_DEBUG("user pwm not init!");
        return OPRT_INVALID_PARM;
    }
    if ((c_value > MAX_DUTY) || (w_value > MAX_DUTY) || (r_value > MAX_DUTY) || (g_value > MAX_DUTY) || (b_value > MAX_DUTY))
    {
        TAL_PR_DEBUG("USER PWM data is invalid!");
        return OPRT_INVALID_PARM;
    }
    if (g_pwm_bPolarity == TUYA_PWM_NEGATIVE)
    {
        r_value = MAX_DUTY - r_value;
        g_value = MAX_DUTY - g_value;
        b_value = MAX_DUTY - b_value;
        c_value = MAX_DUTY - c_value;
        w_value = MAX_DUTY - w_value;
    }
    switch (sg_light_channel)
    {
    case 2:                                  /* 2 way -- CW */
        __app_ctrl_pwm_duty_set(0, c_value); /* send C value */
        __app_ctrl_pwm_duty_set(1, w_value); /* send W value */
        tal_pwm_start(0xFF);
        break;
    default:
        TAL_PR_DEBUG("pwm drive channel set error!!");
        break;
    }
    return OPRT_OK;
}
/**
 * @description: calculate Gain Step by target_value and transition time 
 * @param [in] {target_value} 
 * @param [in] {transition_time} 
 * @return: none
 */
STATIC VOID_T __app_ctrl_shade_gain_and_slope_set(USHORT_T target_value, USHORT_T transition_time)
{
    TAL_PR_DEBUG("target_value =%d  transition_time= %d", target_value, transition_time);
    if (target_value == 0 || transition_time == 0 || transition_time == 0xFFFF)
    {
        TAL_PR_DEBUG("default gain and slope");
        sg_step_gain = SHADE_STEP_GAIN_DEFAULT;
        sg_light_ctrl_handle_data.slope = SHADE_SLOPE_DEFAULT;
        sg_step_gain_float = sg_step_gain;
    }
    else if (target_value <= (transition_time / LIGHT_SHADE_CYCLE))
    {
        TAL_PR_DEBUG("target_value less than transtime/cycle");
        sg_step_gain = 1;
        sg_light_ctrl_handle_data.slope = target_value / 1.0 / transition_time;
        sg_step_gain_float = 1.0;
    }
    else
    {
        TAL_PR_DEBUG("redefine gain and slope");
        sg_step_gain_float = 1.0 * target_value * LIGHT_SHADE_CYCLE / transition_time;
        sg_step_gain = (ULONG_T)sg_step_gain_float + 1;
        sg_light_ctrl_handle_data.slope = target_value / 1.0 / transition_time;
    }

    TAL_PR_DEBUG("step_float =%d  sg_light_ctrl_handle_data.slope = %d", (ULONG_T)(sg_step_gain_float * 100), (ULONG_T)(sg_light_ctrl_handle_data.slope * 100));
}
/**
 * @berief: get change gradually process the max error of 5ways
 * @param {IN BRIGHT_DATA_T *TargetVal -> target set}
 * @param {IN BRIGHT_DATA_T *CurrVal -> current value}
 * @retval: none
 */
STATIC ULONG_T __app_light_ctrl_shade_max_get(LIGHT_DATA_T *TargetVal, LIGHT_DATA_T *CurrVal)
{
    ULONG_T max_value = 0;

    max_value = (ULONG_T)app_light_tools_get_max_value(
        app_light_tools_get_abs_value(TargetVal->red - CurrVal->red),
        app_light_tools_get_abs_value(TargetVal->green - CurrVal->green),
        app_light_tools_get_abs_value(TargetVal->blue - CurrVal->blue),
        app_light_tools_get_abs_value(TargetVal->white - CurrVal->white),
        app_light_tools_get_abs_value(TargetVal->warm - CurrVal->warm));

    return max_value;
}
/**
 * @brief 
 * 
 */
STATIC VOID_T __app_light_ctrl_stop(VOID_T)
{
    sg_usRemainTime100ms = 0;
    sg_shade_flag = FALSE;
    tal_sw_timer_stop(etimer_shande_param);
    tal_sw_timer_stop(etimer_shande_rgbcw);
    TAL_PR_DEBUG("Shade STOP >>>  RGBCW : %d, %d, %d, %d, %d", sg_light_ctrl_handle_data.current_val.red,
                 sg_light_ctrl_handle_data.current_val.green,
                 sg_light_ctrl_handle_data.current_val.blue,
                 sg_light_ctrl_handle_data.current_val.white,
                 sg_light_ctrl_handle_data.current_val.warm);
    TAL_PR_DEBUG("Shade STOP >>>  BT : %d, %d", sg_light_ctrl_handle_data.current_white.bright, sg_light_ctrl_handle_data.current_white.temper);
}

/**
 * @note: light ctrl blink proc
 * @param {none}
 * @return: UCHAR_T
 */
STATIC VOID_T __app_light_ctrl_blink_display(VOID_T)
{
    OPERATE_RET ret = -1;
    STATIC UCHAR_T cnt = 0;
    LIGHT_DATA_T ctrl_data;

    tal_system_memset(&ctrl_data, 0, SIZEOF(LIGHT_DATA_T));
    ctrl_data.white = 0;
    ctrl_data.warm = (cnt % 2) ? 1000 : 0;
    ret = __app_light_ctrl_rgbcw_set(0, 0, 0, ctrl_data.white, ctrl_data.warm);
    TAL_PR_DEBUG("blink...C->%d, W->%d", ctrl_data.white, ctrl_data.warm);
    if (ret != OPRT_OK)
    {
        TAL_PR_DEBUG("blink set RGBCW error!");
    }
    cnt++;
}
/**
 * @description: The dimming end callback, usually because the dimming process receives a STOP command, or the dimming process ends
 * @param {type} none
 * @return: none
 */
STATIC VOID_T __app_light_ctrl_data_move_complete_cb(VOID_T)
{
    TAL_SEND_QOS_E QOS = QOS_VIP_0;
    UCHAR_T level = 0;
    USHORT_T temper = sg_light_ctrl_data.temper;

    level = app_light_tools_B_2_zig_b(sg_light_ctrl_data.bright);
    TAL_PR_DEBUG("Move Complete Callback...");

    tal_zg_write_attribute(1,
                           CLUSTER_LEVEL_CONTROL_CLUSTER_ID,
                           ATTR_CURRENT_LEVEL_ATTRIBUTE_ID,
                           &level,
                           ATTR_INT8U_ATTRIBUTE_TYPE);
    app_level_report_level_value(QOS, 100, level);
    tal_zg_write_attribute(1,
                           CLUSTER_COLOR_CONTROL_CLUSTER_ID,
                           ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID,
                           &temper,
                           ATTR_INT8U_ATTRIBUTE_TYPE);
    app_color_report_temperature_value(QOS, 200, temper);
}
/**
 * @brief 
 * 
 * @param app_data 
 * @return UINT8_T 
 */
STATIC UINT8_T __app_light_ctrl_write_app_data_attr(APP_ALL_DATA_T *app_data)
{
    UCHAR_T buffer[sizeof(APP_ALL_DATA_T) + 1] = {0};
    if (app_data == NULL)
    {
        TAL_PR_DEBUG("app_data ERROR");
        return FALSE;
    }
    buffer[0] = sizeof(APP_ALL_DATA_T);
    tal_system_memcpy(&buffer[1], app_data, sizeof(APP_ALL_DATA_T));
    TAL_PR_HEXDUMP_DEBUG("app_data:", buffer, buffer[0] + 1);
    OPERATE_RET ret = tal_zg_write_attribute(1,
                                             CLUSTER_COLOR_CONTROL_CLUSTER_ID,
                                             TY_ATTR_LIGHT_APP_DATA_ATTRIBUTE_ID,
                                             buffer,
                                             ATTR_ARRAY_ATTRIBUTE_TYPE);
    if (ret != OPRT_OK)
    {
        TAL_PR_DEBUG("Write app_data ERROR:%d", ret);
        return FALSE;
    }
    return TRUE;
}

/**
 * @note: calculate the CW Light_Handle.TargetVal according to
 *          the Light_Data value, and cw output limit and amplify process!
 * @param {IN USHORT_T Bright -> Light_Data bright }
 * @param {IN USHORT_T Temper -> Light_Data temperature}
 * @param {IN LIGHT_DATA_T *Result -> calc result}
 * @return: none
 */
VOID_T app_light_ctrl_data_calc_cw(USHORT_T bright, USHORT_T temper, LIGHT_DATA_T *result)
{
    USHORT_T bright_temp = 0;

    if (bright < CTRL_CW_BRIGHT_VALUE_MIN)
    {
        bright_temp = 0;
    }
    else
    {
        ///<  limit CW bright output limit
        bright_temp = __app_light_ctrl_data_cw_limit(bright);
    }

    ///<  power amplification
    bright_temp = (USHORT_T)(((FLOAT_T)(100 / CTRL_POEWER_GAIN_MIN)) * bright_temp);
    result->white = bright_temp * temper / CTRL_CAL_VALUE_RANGE;
    result->warm = bright_temp - result->white;

    //output max limit --  power amplification, convert overflow power to another
    if (result->white > CTRL_CAL_VALUE_RANGE)
    {
        result->warm = (USHORT_T)(result->warm * ((FLOAT_T)(result->white / CTRL_CAL_VALUE_RANGE)));
        result->white = CTRL_CAL_VALUE_RANGE;
    }
    if (result->warm > CTRL_CAL_VALUE_RANGE)
    {
        result->white = (USHORT_T)(result->white * ((FLOAT_T)(result->warm / CTRL_CAL_VALUE_RANGE)));
        result->warm = CTRL_CAL_VALUE_RANGE;
    }
}
/**
 * @note: set light switch data, adapte control data issued by system
 *          to control data format.
 * @param {IN UINT8_T on_off -> switch data, TRUE will turn on}
 * @return: OPERATE_RET -> OPRT_OK meaning need to call app_light_ctrl_proc() function!
 */
OPERATE_RET app_light_ctrl_data_switch_set(UINT8_T on_off)
{
    UINT8_T last_status;

    last_status = sg_light_ctrl_data.onoff_status;

    if (TRUE == on_off)
    {
        sg_light_ctrl_data.onoff_status = TRUE;
    }
    else
    {
        sg_light_ctrl_data.onoff_status = FALSE;
    }
    if (on_off == last_status)
    {
        TAL_PR_DEBUG("the same switch set");
        return OPRT_INVALID_PARM;
    }

    return OPRT_OK;
}
/**
 * @note: set light count down cnt
 * @param [in] {ULONG_T}count
 * @return: none
 */
VOID_T app_light_count_down_cnt_set(ULONG_T count)
{
    sg_light_ctrl_data.count_down_cnt = count;
}
/**
 * @note: return app_light_count_down_cnt_get
 * @param [in] none
 * @return: app_light_count_down_cnt_get
 */
ULONG_T app_light_count_down_cnt_get(VOID_T)
{
    return sg_light_ctrl_data.count_down_cnt;
}
/**
 * @note: set light mode data
 * @param {IN LIGHT_MODE_E Mode}
 * @attention:Mode value is below:
 *                                  WHITE_MODE = 0,
 *                                  COLOR_MODE = 1,
 *                                  SCENE_MODE = 2,
 *                                  MUSIC_MODE = 3,
 * @return: OPERATE_RET -> OPRT_OK meaning need to call app_light_ctrl_proc() function!
 */
OPERATE_RET app_light_ctrl_data_mode_set(LIGHT_MODE_E mode)
{
    LIGHT_MODE_E last_mode;
    last_mode = sg_light_ctrl_data.mode;
    if (mode > MODE_MAX)
    {
        TAL_PR_DEBUG("mode is illegal,set error");
        return OPRT_INVALID_PARM;
    }
    sg_light_ctrl_data.mode = mode;
    if (mode == last_mode)
    {
        //only scene mode can be set many time
        TAL_PR_DEBUG("the same mode set");
        return OPRT_INVALID_PARM;
    }
    return OPRT_OK;
}
/**
 * @note: set light bright data, adapte control data issued by system
 *          to control data format.
 * @param {IN USHORT_T bright}
 * @attention: acceptable range:10~1000
 * @attention: set bright value, will auto set the Mode to WHITE_MODE !
 * @return: OPERATE_RET -> OPRT_OK meaning need to call app_light_ctrl_proc() function!
 */
OPERATE_RET app_light_ctrl_data_bright_set(USHORT_T bright, USHORT_T transtime)
{
    USHORT_T last_bright;
    USHORT_T diff = 0;

    last_bright = sg_light_ctrl_data.bright;

    if (bright < CTRL_CW_BRIGHT_VALUE_MIN)
    {
        TAL_PR_DEBUG("bright value is exceed range,set error");
        bright = CTRL_CW_BRIGHT_VALUE_MIN;
    }

    if (bright > CTRL_CW_BRIGHT_VALUE_MAX)
    {
        TAL_PR_DEBUG("bright value is exceed range,set error");
        bright = CTRL_CW_BRIGHT_VALUE_MAX;
        sg_light_ctrl_data.onoff_status = TRUE;
    }

    sg_light_ctrl_data.bright = bright;
    sg_light_ctrl_handle_data.target_white.bright = bright;
    sg_light_ctrl_data.mode = WHITE_MODE;

    if (bright == last_bright)
    {
        TAL_PR_DEBUG("the same bright set");
        return OPRT_COM_ERROR;
    }
    else
    {
        sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_B;
    }
    if (sg_shade_flag)
    {
        sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_RGBCW;
    }
    if (transtime != 0)
    {
        diff = (USHORT_T)app_light_tools_get_abs_value(bright - last_bright);
        __app_ctrl_shade_gain_and_slope_set(diff, transtime * 100);
    }
    else
    {
        __app_ctrl_shade_gain_and_slope_set(0, 0);
    }

    return OPRT_OK;
}
/**
 * @note: get light switch data
 * @param {OUT UINT8_T *onff -> switch data return}
 * @return: OPERATE_RET
 */
USHORT_T app_light_ctrl_handle_data_bright_get(VOID_T)
{
    return sg_light_ctrl_handle_data.current_white.bright;
}
/**
 * @note: set light temrperature data, adapte control data issued by system
 *          to control data format.
 * @param {IN USHORT_T temperature}
 * @attention: acceptable range:0~1000
 * @return: OPERATE_RET -> OPRT_OK meaning need to call app_light_ctrl_proc() function!
 */
OPERATE_RET app_light_ctrl_data_temp_set(USHORT_T temperature, USHORT_T transtime)
{
    USHORT_T last_temperature;
    USHORT_T diff = 0;

    last_temperature = sg_light_ctrl_data.temper;
    TAL_PR_DEBUG("sg_light_ctrl_data.temper =%d , temperature=%d,", sg_light_ctrl_data.temper, temperature);
    if (temperature > CTRL_CW_BRIGHT_VALUE_MAX)
    {
        TAL_PR_DEBUG("temperature value is exceed range,set error");
        sg_light_ctrl_data.temper = 0;
        return OPRT_INVALID_PARM;
    }

    sg_light_ctrl_data.temper = temperature;
    sg_light_ctrl_handle_data.target_white.temper = temperature;

    sg_light_ctrl_data.mode = WHITE_MODE;
    if (temperature == last_temperature)
    {
        TAL_PR_DEBUG("the same temperature set");
        return OPRT_INVALID_PARM;
    }
    else
    {
        sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_T;
    }
    if (sg_shade_flag)
    {
        sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_RGBCW;
    }
    if (transtime != 0)
    {
        diff = (USHORT_T)app_light_tools_get_abs_value(temperature - last_temperature);
        __app_ctrl_shade_gain_and_slope_set(diff, transtime * 100);
    }
    else
    {
        __app_ctrl_shade_gain_and_slope_set(0, 0);
    }
    return OPRT_OK;
}
/**
 * @note: get light switch data
 * @param {OUT UINT8_T *onff -> switch data return}
 * @return: OPERATE_RET
 */
OPERATE_RET app_light_ctrl_data_switch_get(UINT8_T *on_off)
{
    *on_off = sg_light_ctrl_data.onoff_status;

    return OPRT_OK;
}
/**
 * 
 * @note: geta light bright data
 * @param {OUT USHORT_T *pBright -> bright data return}
 * @return: OOPERATE_RET
 */
OPERATE_RET app_light_ctrl_data_bright_get(USHORT_T *bright)
{

    *bright = sg_light_ctrl_data.bright;

    return OPRT_OK;
}
/**
 * @note: get light temrperature data
 * @param {OUT USHORT_T *pTemperature -> temperature data return}
 * @return: OPERATE_RET
 */
OPERATE_RET app_light_ctrl_data_temper_get(USHORT_T *temperature)
{
    *temperature = sg_light_ctrl_data.temper;

    return OPRT_OK;
}
/**
 * @note: set light temrperature data
 * @param [in] {USHORT_T } temperature
 * @return: OPERATE_RET
 */
VOID_T app_light_ctrl_data_temper_set(USHORT_T temperature)
{
    sg_light_ctrl_data.temper = temperature;
}
/**
 * @note: get light mode data
 * @param {OUT LIGHT_MODE_E *Mode -> mode data return}
 * @return: OPERATE_RET
 */
OPERATE_RET app_light_ctrl_data_mode_get(LIGHT_MODE_E *mode)
{
    *mode = sg_light_ctrl_data.mode;

    return OPRT_OK;
}
/**
 * @note: get control ctrl data
 * @param {none}
 * @return: LIGHT_CTRL_DATA_T
 */
LIGHT_CTRL_DATA_T app_light_ctrl_data_get(VOID_T)
{
    return sg_light_ctrl_data;
}
/**
 * @brief 
 * 
 * @param timer_id 
 * @param arg 
 */
VOID_T app_light_ctrl_shade_param(TIMER_ID timer_id, VOID_T *arg)
{
    //TAL_PR_DEBUG("Run Into>>>%s",__FUNCTION__);
    OPERATE_RET opRet = -1;
    LIGHT_DATA_T v_shade_rgbcw_data;
    STATIC ULONG_T shade_cnt = 0;
    STATIC ULONG_T shade_limit_cnt = 0;
    STATIC ULONG_T step_cnt = 0;

    STATIC ULONG_T current_shade_value = 0;
    STATIC FLOAT_T current_shade_value_float = 0;
    STATIC ULONG_T target_shade_value = 0;

    STATIC FLOAT_T target_gain = 0;
    USHORT_T actual_gain = 0;
    LONG_T v_level_delta = 0;

    tal_sw_timer_start(etimer_shande_param, LIGHT_SHADE_CYCLE, TAL_TIMER_ONCE);

    tal_system_memset(&v_shade_rgbcw_data, 0, sizeof(LIGHT_DATA_T));

    if (sg_light_ctrl_handle_data.first_change_flag)
    {
        sg_light_ctrl_handle_data.first_change_flag = FALSE;
        switch (sg_light_ctrl_handle_data.shade_param)
        {
        case SHADE_PARAM_B:
        {
            current_shade_value = sg_light_ctrl_handle_data.current_white.bright;
            target_shade_value = sg_light_ctrl_handle_data.target_white.bright;
            current_shade_value_float = current_shade_value;
            TAL_PR_DEBUG("bright shade: %d >> %d", current_shade_value, target_shade_value);
            break;
        }
        case SHADE_PARAM_T:
        {
            current_shade_value = sg_light_ctrl_handle_data.current_white.temper;
            target_shade_value = sg_light_ctrl_handle_data.target_white.temper;
            current_shade_value_float = current_shade_value;
            TAL_PR_DEBUG("Temper shade: %d >> %d", current_shade_value, target_shade_value);
            break;
        }
        default:
        {
            shade_cnt = 0;
            step_cnt = 0;
            return;
        }
        }
        shade_cnt = 0;
        step_cnt = 0;
        TAL_PR_DEBUG("level_delta: %d", current_shade_value - target_shade_value);
    }
    v_level_delta = (LONG_T)current_shade_value - (LONG_T)target_shade_value;

    if (v_level_delta != 0)
    {

        sg_usRemainTime100ms = (USHORT_T)(app_light_tools_get_abs_value(v_level_delta) / sg_light_ctrl_handle_data.slope / 100); ///< ?

        shade_cnt++;
        target_gain = sg_light_ctrl_handle_data.slope * (1.0) * (shade_cnt * LIGHT_SHADE_CYCLE);
        actual_gain = (SHORT_T)(step_cnt * sg_step_gain_float);
        if ((target_gain - actual_gain) >= sg_step_gain_float)
        {
            step_cnt++;
            if (app_light_tools_get_abs_value(v_level_delta) <= sg_step_gain_float)
            {
                current_shade_value = target_shade_value;
                step_cnt = 0;
                shade_cnt = 0;
            }
            else
            {
                if (v_level_delta < 0)
                {
                    current_shade_value_float += sg_step_gain_float;
                    current_shade_value = (ULONG_T)current_shade_value_float;
                }
                else
                {
                    current_shade_value_float -= sg_step_gain_float;
                    current_shade_value = (ULONG_T)current_shade_value_float;
                }
            }

            switch (sg_light_ctrl_handle_data.shade_param)
            {
            case SHADE_PARAM_B:
            {
                sg_light_ctrl_handle_data.current_white.bright = current_shade_value;
                break;
            }
            case SHADE_PARAM_T:
            {
                sg_light_ctrl_handle_data.current_white.temper = current_shade_value;
                break;
            }
            default:
                break;
            }
            app_light_ctrl_data_calc_cw(sg_light_ctrl_handle_data.current_white.bright, sg_light_ctrl_handle_data.current_white.temper, &v_shade_rgbcw_data);

            tal_system_memcpy(&sg_light_ctrl_handle_data.current_val, &v_shade_rgbcw_data, sizeof(LIGHT_DATA_T));
            opRet = __app_light_ctrl_rgbcw_set(0, 0, 0, v_shade_rgbcw_data.white, v_shade_rgbcw_data.warm);

            shade_limit_cnt++;
            if (shade_limit_cnt > 600)
            {
                //2S = 400 * LIGHT_SHADE_CYCLE
                TAL_PR_DEBUG("WATCHDOG_RELOAD");
                tal_watchdog_refresh();
                shade_limit_cnt = 0;
            }
            if (opRet != OPRT_OK)
            {
                TAL_PR_DEBUG("Set RGBCW Error");
            }

#if shade_log_en
            TAL_PR_DEBUG("RGBCW : %d, %d, %d, %d, %d", v_shade_rgbcw_data.red,
                         v_shade_rgbcw_data.green,
                         v_shade_rgbcw_data.blue,
                         v_shade_rgbcw_data.white,
                         v_shade_rgbcw_data.warm);

            TAL_PR_DEBUG("BT : %d, %d", sg_light_ctrl_handle_data.current_white.bright,
                         sg_light_ctrl_handle_data.current_white.temper);
#endif
        }
        // TAL_PR_DEBUG("current_shade_value: %d, target_shade_value=%d", current_shade_value, target_shade_value);
    }
    else
    {
        TAL_PR_DEBUG("LEVEL_delta=0");
        shade_limit_cnt = 0;
        tal_system_memcpy(&sg_light_ctrl_handle_data.current_white, &sg_light_ctrl_handle_data.target_white, sizeof(COLOR_WHITE_T));
        tal_system_memcpy(&sg_light_ctrl_handle_data.current_val, &sg_light_ctrl_handle_data.target_val, sizeof(LIGHT_DATA_T));
        __app_light_ctrl_stop();

        sg_light_ctrl_handle_data.slope = SHADE_SLOPE_DEFAULT;
        sg_step_gain = SHADE_STEP_GAIN_DEFAULT;
        if (FALSE == sg_light_ctrl_data.onoff_status && identify_processing_flag != TRUE)
        {
            opRet = __app_light_ctrl_rgbcw_set(0, 0, 0, 0, 0);
            if (opRet != OPRT_OK)
            {
                TAL_PR_DEBUG("SET RGBCW error %d!", opRet);
            }
        }
        else
        {
            if (sg_level_move_down_to_off == TRUE)
            {
                sg_level_move_down_to_off = FALSE;
                opRet = __app_light_ctrl_rgbcw_set(0, 0, 0, 0, 0);
                if (opRet != OPRT_OK)
                {
                    TAL_PR_DEBUG("SET RGBCW error %d!", opRet);
                }

                app_onoff_data_count_down_set(0);

                sg_light_ctrl_data.onoff_status = FALSE;
                sg_light_ctrl_handle_data.current_white.bright = 0;
                tal_system_memset(&sg_light_ctrl_handle_data.current_val, 0, sizeof(LIGHT_DATA_T));
            }
            else
            {
            }
        }

        //save data
        //TAL_PR_DEBUG("tal_sw_timer_start: %d", tal_sw_timer_start(etimer_save_data_delay, 2000, TAL_TIMER_ONCE));
        tal_sw_timer_start(etimer_save_data_delay, 2000, TAL_TIMER_ONCE);
    }
}
/**
 * @description: 在RGBCW输出值的维度进行渐变,每一个颜色都会根据到达目的点的差值和指定的渐变时间,计算斜率.
 * 采用相同的步进值,该步进值根据差值最大的颜色计算.
 * @param {type} none
 * @return: none
 */
VOID_T app_light_ctrl_shade_rgbcw(TIMER_ID timer_id, VOID_T *arg)
{
    //TAL_PR_DEBUG("Run Into>>>%s",__FUNCTION__);
    OPERATE_RET opRet = -1;
    STATIC ULONG_T shade_limit_cnt = 0;
    STATIC ULONG_T shade_cnt = 0; //渐变次数

    STATIC ULONG_T shade_cnt_c = 0; //渐变次数
    STATIC ULONG_T shade_cnt_w = 0; //渐变次数

    LONG_T error_c = 0;
    LONG_T error_w = 0;

    USHORT_T max_error = 0;

    STATIC FLOAT_T scale_c = 0;
    STATIC FLOAT_T scale_w = 0;

    FLOAT_T target_step_c = 0;
    FLOAT_T target_step_w = 0;

    ULONG_T act_step_c = 0;
    ULONG_T act_step_w = 0;

    tal_sw_timer_start(etimer_shande_rgbcw, LIGHT_SHADE_CYCLE, TAL_TIMER_ONCE);
    if (memcmp(&sg_light_ctrl_handle_data.target_val, &sg_light_ctrl_handle_data.current_val, sizeof(LIGHT_DATA_T)) != 0)
    { /* exist error */
        error_c = sg_light_ctrl_handle_data.current_val.white - sg_light_ctrl_handle_data.target_val.white;
        error_w = sg_light_ctrl_handle_data.current_val.warm - sg_light_ctrl_handle_data.target_val.warm;

        max_error = __app_light_ctrl_shade_max_get(&sg_light_ctrl_handle_data.target_val, &sg_light_ctrl_handle_data.current_val);
        if (sg_light_ctrl_handle_data.slope == 0)
        { // in case of 0 as divisor
            sg_light_ctrl_handle_data.slope = SHADE_SLOPE_DEFAULT;
        }
        sg_usRemainTime100ms = (USHORT_T)(app_light_tools_get_abs_value(max_error) / sg_light_ctrl_handle_data.slope / 100);
        shade_cnt++;

        if (TRUE == sg_light_ctrl_handle_data.first_change_flag)
        {
            sg_light_ctrl_handle_data.first_change_flag = FALSE;

            // gradually change not need change gain and slope
            if (sg_transtime_100ms != 0)
            {
                __app_ctrl_shade_gain_and_slope_set(app_light_tools_get_abs_value(max_error), sg_transtime_100ms * 100); //计算差值最大者的斜率和步进,再重新计算各灯珠斜率,采用相同步进
                sg_transtime_100ms = 0;
            }

            scale_c = app_light_tools_get_abs_value(error_c) / 1.0 / max_error * sg_light_ctrl_handle_data.slope;
            scale_w = app_light_tools_get_abs_value(error_w) / 1.0 / max_error * sg_light_ctrl_handle_data.slope;
        }

        target_step_c = scale_c * (1.0) * (shade_cnt * LIGHT_SHADE_CYCLE);
        target_step_w = scale_w * (1.0) * (shade_cnt * LIGHT_SHADE_CYCLE);

        act_step_c = shade_cnt_c * sg_step_gain;
        act_step_w = shade_cnt_w * sg_step_gain;

        if (error_c != 0)
        {
            if (target_step_c - act_step_c >= sg_step_gain)
            {
                shade_cnt++;
                if (app_light_tools_get_abs_value(error_c) <= sg_step_gain)
                {
                    sg_light_ctrl_handle_data.current_val.white = sg_light_ctrl_handle_data.target_val.white;
                    TAL_PR_DEBUG("shade_cnt_c :%d", shade_cnt_c);
                    shade_cnt_c = 0;
                }
                else
                {
                    if (error_c < 0)
                    {
                        sg_light_ctrl_handle_data.current_val.white += sg_step_gain;
                    }
                    else
                    {
                        sg_light_ctrl_handle_data.current_val.white -= sg_step_gain;
                    }
                }
            }
        }

        if (error_w != 0)
        {
            if (target_step_w - act_step_w >= sg_step_gain)
            {
                shade_cnt_w++;
                if (app_light_tools_get_abs_value(error_w) <= sg_step_gain)
                {
                    sg_light_ctrl_handle_data.current_val.warm = sg_light_ctrl_handle_data.target_val.warm;
                    TAL_PR_DEBUG("shade_cnt_w :%d", shade_cnt_w);
                    shade_cnt_w = 0;
                }
                else
                {
                    if (error_w < 0)
                    {
                        sg_light_ctrl_handle_data.current_val.warm += sg_step_gain;
                    }
                    else
                    {
                        sg_light_ctrl_handle_data.current_val.warm -= sg_step_gain;
                    }
                }
            } // else G don't need move
        }

        opRet = __app_light_ctrl_rgbcw_set(0, 0, 0, sg_light_ctrl_handle_data.current_val.white, sg_light_ctrl_handle_data.current_val.warm);
        shade_limit_cnt++;
        if (shade_limit_cnt > 100)
        {                           //2S = 400 * LIGHT_SHADE_CYCLE
            tal_watchdog_refresh(); //TODO
            shade_limit_cnt = 0;
        }
        if (opRet != OPRT_OK)
        {
            TAL_PR_DEBUG("Set RGBCW Error");
        }
#if shade_log_en
        TAL_PR_DEBUG("RGBCW : %d, %d, %d, %d, %d", sg_light_ctrl_handle_data.current_val.red,
                     sg_light_ctrl_handle_data.current_val.green,
                     sg_light_ctrl_handle_data.current_val.blue,
                     sg_light_ctrl_handle_data.current_val.white,
                     sg_light_ctrl_handle_data.current_val.warm);
#endif
    }
    else
    {
        TAL_PR_DEBUG("NO_LEVER_DELTA");
        shade_limit_cnt = 0;
        shade_cnt = 0;
        __app_light_ctrl_stop(); /* just stop the change */

        tal_system_memcpy(&sg_light_ctrl_handle_data.current_white, &sg_light_ctrl_handle_data.target_white, sizeof(COLOR_WHITE_T));
        tal_system_memcpy(&sg_light_ctrl_handle_data.current_val, &sg_light_ctrl_handle_data.target_val, sizeof(LIGHT_DATA_T));
        __app_light_ctrl_rgbcw_set(0, 0, 0, sg_light_ctrl_handle_data.current_val.white, sg_light_ctrl_handle_data.current_val.warm);
        //save data
        tal_sw_timer_start(etimer_save_data_delay, 2000, TAL_TIMER_ONCE);
    }
}
/**
 * @note: Light control proc
 * @param {none}
 * @return: OPERATE_RET
 */
OPERATE_RET app_light_ctrl_proc(VOID_T)
{
    STATIC UINT8_T last_switch_status = FALSE;
    TAL_PR_DEBUG("sg_light_ctrl_data.onoff_status = %d, last_switch_status=%d", sg_light_ctrl_data.onoff_status, last_switch_status);
    if (sg_light_ctrl_data.onoff_status == FALSE)
    {
        ///< OFF
        __app_light_ctrl_stop();
        tal_system_memset(&sg_light_ctrl_handle_data.target_val, 0, sizeof(LIGHT_DATA_T));
        sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_B;
        sg_light_ctrl_handle_data.target_white.bright = 0;
        __app_ctrl_shade_gain_and_slope_set(CTRL_CW_BRIGHT_VALUE_MAX, sg_shade_off_time);
        __app_light_ctrl_start();
    }
    else
    {
        ///< ON
        if (last_switch_status == FALSE)
        {
            ///< light is off
            sg_light_ctrl_handle_data.shade_param = SHADE_PARAM_B;
            sg_light_ctrl_handle_data.target_white.bright = sg_light_ctrl_data.bright;
            sg_light_ctrl_handle_data.current_white.temper = sg_light_ctrl_handle_data.target_white.temper;
            TAL_PR_DEBUG("sg_light_ctrl_handle_data.target_white.bright = %d, sg_light_ctrl_data.bright= %d", sg_light_ctrl_handle_data.target_white.bright, sg_light_ctrl_data.bright);
            __app_ctrl_shade_gain_and_slope_set(CTRL_CW_BRIGHT_VALUE_MAX, sg_shade_on_time);
        }
        app_light_ctrl_data_calc_cw(sg_light_ctrl_data.bright, sg_light_ctrl_data.temper, &sg_light_ctrl_handle_data.target_val);
        __app_light_ctrl_start();
    }
    last_switch_status = sg_light_ctrl_data.onoff_status;
    return OPRT_OK;
}
/**
 * @note: light ctrl blink timer callback
 * @param {none}
 * @return: none
 */
VOID_T app_light_ctrl_blink_timer_cb(TIMER_ID timer_id, VOID_T *arg)
{
    __app_light_ctrl_blink_display();
    tal_sw_timer_start(etimer_blink_sw, sg_blink_time_ms, TAL_TIMER_ONCE);
}
/**
 * @description: start blink
 * @param {IN ULONG_T blink_time -> blink phase}
 * @attention: blink display will as the parm
 *             -- NetColor, usNetBright in configuration.
 * @return: none
 */
OPERATE_RET app_light_ctrl_blink_start(ULONG_T blink_time)
{
    sg_blink_time_ms = 1000;
    __app_light_ctrl_stop();
    tal_sw_timer_start(etimer_blink_sw, 10, TAL_TIMER_ONCE);

    return OPRT_OK;
}
/**
 * @brief 
 * 
 * @param timer_id 
 * @param arg 
 * @return VOID_T 
 */
VOID_T app_light_ctrl_mf_blink_cb(TIMER_ID timer_id, VOID_T *arg)
{
    OPERATE_RET ret = -1;
    STATIC UCHAR_T cnt = 0;
    LIGHT_DATA_T ctrl_data;

    STATIC USHORT_T sv_2min = 4 * 60 * 2; //2min

    TAL_PR_DEBUG("Run Into>>>%s", __FUNCTION__);
    tal_system_memset(&ctrl_data, 0, SIZEOF(LIGHT_DATA_T));
    ctrl_data.white = (cnt % 2) ? 1000 : 0;
    ctrl_data.warm = 0;
    ret = __app_light_ctrl_rgbcw_set(0, 0, 0, ctrl_data.white, ctrl_data.warm);
    TAL_PR_DEBUG("blink...C->%d, W->%d", ctrl_data.white, ctrl_data.warm);
    if (ret != OPRT_OK)
    {
        TAL_PR_DEBUG("blink set RGBCW error!");
    }
    cnt++;

    sv_2min--;
    if (sv_2min)
    {
        tal_sw_timer_start(etimer_mf_blink, 250, TAL_TIMER_ONCE);
    }
    else
    {
        tal_sw_timer_delete(etimer_mf_blink);
        __app_light_ctrl_rgbcw_set(0, 0, 0, 0, 0);
    }
}
/**
 * @description: stop blink
 * @param {type} none
 * @attention: blink stop will directly go to normal status display
 *              normal status will bright as default bright parm
 *              -- usDefBright,usDefTemper,DefColor in configuration.
 * @return: none
 */
OPERATE_RET app_light_ctrl_blink_stop(VOID_T)
{
    tal_sw_timer_stop(etimer_blink_sw);

    __app_light_ctrl_rgbcw_set(0, 0, 0, 0, 1000);
    sg_light_ctrl_handle_data.current_val.white = 0;
    sg_light_ctrl_handle_data.current_val.warm = 1000;
    sg_light_ctrl_handle_data.current_white.bright = 1000;
    sg_light_ctrl_handle_data.current_white.temper = 0;

    app_light_ctrl_data_switch_set(1);
    app_light_ctrl_data_mode_set(WHITE_MODE);
    app_light_ctrl_data_bright_set(1000, 0);
    app_light_ctrl_data_temp_set(0, 0);

    app_light_ctrl_proc();

    tal_sw_timer_start(etimer_join_end_sync, 10000, TAL_TIMER_ONCE);
    tal_sw_timer_start(etimer_save_data_delay, 10, TAL_TIMER_ONCE);
    return OPRT_OK;
}
/**
 * @brief Set the pwm channel num object
 * 
 * @param channel_num 
 */
VOID_T app_light_ctrl_ch_num_set(UCHAR_T channel_num)
{
    sg_light_channel = channel_num;
}
/**
 * @brief Set the light shade param object
 * 
 * @param shade_param 
 */
VOID_T app_light_ctrl_shade_param_set(SHADE_PARAM_E shade_param)
{
    sg_light_ctrl_handle_data.shade_param = shade_param;
}
/**
 * @description: Move to off when shade stop
 * @param {type} bMoveToOff
 * @return:
 */
VOID_T app_light_ctrl_move_down_to_off_flag_set(VOID_T)
{
    sg_level_move_down_to_off = TRUE;
}
/**
 * @description: Get shade remain time
 * @param [OUT]{RemainTime100ms} remain time: 100ms
 * @return: none
 */
VOID_T app_light_ctrl_remain_time_ms_get(USHORT_T *remain_time_100ms)
{
    *remain_time_100ms = sg_usRemainTime100ms;
}
/**
 * @description: stop step level or move level
 * @param {type} none
 * @return: none
 */
OPERATE_RET app_light_ctrl_data_move_stop(VOID_T)
{
    tal_sw_timer_stop(etimer_shande_param);

    sg_level_move_down_to_off = FALSE;

    sg_step_gain = (SHADE_STEP_GAIN_DEFAULT);

    tal_system_memcpy(&sg_light_ctrl_handle_data.target_white, &sg_light_ctrl_handle_data.current_white, sizeof(COLOR_WHITE_T));

    TAL_PR_DEBUG(" in app_light_ctrl_data_move_stop ");

    sg_light_ctrl_data.bright = sg_light_ctrl_handle_data.current_white.bright;
    sg_light_ctrl_data.temper = sg_light_ctrl_handle_data.current_white.temper;

    __app_light_ctrl_data_move_complete_cb();
    return OPRT_OK;
}
/**
 * @description: set sg_level_move_down_to_off value
 * @param [in] {UINT8_T} flag
 * @return: none
 */
VOID_T app_light_ctrl_level_move_down_to_off_set(UINT8_T flag)
{
    sg_level_move_down_to_off = flag;
}

/**
 * @brief 
 * 
 * @param timer_id 
 * @param arg 
 * @return STATIC 
 */
VOID_T app_light_ctrl_save_data_delay_cb(TIMER_ID timer_id, VOID_T *arg)
{
    TAL_PR_DEBUG("Run Into>>>%s", __FUNCTION__);
    APP_DATA_T v_app_dcb = {
        .onoff = sg_light_ctrl_data.onoff_status,
        .bright = sg_light_ctrl_data.bright,
        .temperature = sg_light_ctrl_data.temper,
    };

    TAL_KV_STATUS_T v_ret =TAL_KV_SUCC;
    v_ret=tal_kv_flash_write(KV_ID_APP_DATA, sizeof(APP_DATA_T),  (UCHAR_T *)&v_app_dcb);
    if (v_ret !=TAL_KV_SUCC){
        TAL_PR_DEBUG("update app data error.");
        return;
    }

    APP_ALL_DATA_T v_dcb = {
        .onoff = sg_light_ctrl_data.onoff_status,
        .mode = sg_light_ctrl_data.mode,
        .B = sg_light_ctrl_data.bright,
        .T = sg_light_ctrl_data.temper,
    };
    __app_light_ctrl_write_app_data_attr(&v_dcb);
}
/**
 * @brief 
 * 
 * @param timer_id 
 * @param arg 
 * @return VOID_T 
 */
VOID_T app_light_ctrl_power_on_sync_cb(TIMER_ID timer_id, VOID_T *arg)
{

    TAL_PR_DEBUG("Power On Sync Light State.");

    app_light_ctrl_data_bright_get(&g_power_on_bright);
    app_light_ctrl_data_temper_get(&g_power_on_temperature);
    app_light_ctrl_data_switch_get((UINT8_T *)&g_power_on_state);

    app_onoff_report_value(QOS_VIP_0, 0, g_power_on_state);
    app_color_report_ty_temperature_value(QOS_VIP_0, 200, g_power_on_temperature);
    app_level_report_ty_bright_value(QOS_VIP_0, 400, g_power_on_bright);
    app_onoff_report_count_down_data(QOS_1, 800, 0);
    //update on/off attribute
    tal_zg_write_attribute(1,
                           CLUSTER_ON_OFF_CLUSTER_ID,
                           ATTR_ON_OFF_ATTRIBUTE_ID,
                           &g_power_on_state,
                           ATTR_BOOLEAN_ATTRIBUTE_TYPE);
}

/**
 * @brief 
 * 
 * @param timer_id 
 * @param arg 
 * @return VOID_T 
 */
VOID_T app_light_ctrl_join_end_sync_cb(TIMER_ID timer_id, VOID_T *arg)
{

    TAL_PR_DEBUG("join end Sync Light State.");

    app_onoff_report_value(QOS_1, 0, sg_light_ctrl_data.onoff_status); ///<  report data need
    app_color_report_mode_value(QOS_1, 200, WHITE_MODE);

    app_color_report_ty_temperature_value(QOS_VIP_0, 400, sg_light_ctrl_data.temper);
    app_level_report_ty_bright_value(QOS_VIP_0, 600, sg_light_ctrl_data.bright);

    tal_zg_write_attribute(1,
                           CLUSTER_ON_OFF_CLUSTER_ID,
                           ATTR_ON_OFF_ATTRIBUTE_ID,
                           &sg_light_ctrl_data.onoff_status,
                           ATTR_BOOLEAN_ATTRIBUTE_TYPE);
}
